// Author	: damphat@gmail.com
// Created	: 01/12/2008

#pragma once
#include "vn/IOCommon.h"
#include "vn/Mem.h"


IO_BEGIN

struct BufferedStream
// : public Stream
{
public:
	Stream *p_stm;
	Buf8*	p_buffer;
	Int64	m_count;
	Int64	m_capacity;

public:
	enum ReturnType {
		Success,
		NotEnough,
		CapLimit,
		NewLineByEOF,
		NewLineByCR,
		NewLineByLF,
		NewLineByCRLF
	};

public:
	BufferedStream(Stream *pstm, size_t initialCapacity = 128) {
		FALTAL(pstm->CanRead());

		p_stm		= pstm;
		p_buffer	= new Buf8[initialCapacity];
		m_capacity	= initialCapacity;
		m_count		= 0;
	}

	void Remove(Int64 rmv) {
		CHECK_ARG(rmv <= m_count);
		Mem::Copy(&p_buffer[0], &p_buffer[rmv], m_count - rmv);
		m_count -= rmv;
	}

	int PrepareBufferNewLine(ReturnType &type, Int64 limit = (1 << 20)) {
		for (int i=0; i<limit; i++) {
			if(PrepareBuffer(i+1) == NotEnough) {
				if (m_count > 0)
					type = NewLineByEOF;
				else
					type = NotEnough;

				return i;
			}

			if(p_buffer[i] == '\n') {
				type = NewLineByLF;
				return i;
			}

			if(p_buffer[i] == '\r') {
				if(PrepareBuffer(i+2) == Success)
					if(p_buffer[i+1] == '\n')
						type = NewLineByCRLF;
					else
						type = NewLineByCR;
				else
					type = NewLineByCR;

				return i;
			}
		}
		return CapLimit;
	}


	ReturnType EnsureFound(Int64 &cut, const Buf8 *buf, Int64 len, Int64 limit = (1 << 20)) {
		CHECK_ARG_BUFFER(buf, len);

		for (int i=0; i<limit; i++) {
			if(PrepareBuffer(len + i) == NotEnough) {
				cut = -1;
				return NotEnough;
			}

			if(Mem::Compare(&p_buffer[i], buf, len) == 0) {
				cut = i+len;
				return Success;
			}
		}
		cut = -1;
		return CapLimit;
	}

	void PrepareCapacity(Int64 require) {
		if(m_capacity < require) {
			Buf8 *temp = new Buf8[require*2+1];
			m_capacity = require*2+1;
			Mem::Copy(temp, p_buffer, m_count);
			delete[] p_buffer;
			p_buffer = temp;
		}
	}

	ReturnType PrepareBuffer(Int64 require) {
		CHECK_ARG(require >= 0);
		PrepareCapacity(require);
		while(m_count < require) {
			StmLen n = p_stm->Read(p_buffer + m_count, require - m_count);
			if(n == 0) {
				return NotEnough;
			}
			m_count += n;
		}
		return Success;
	}

	Buf8 *GetBufferPtr() {
		return p_buffer;
	}

	Int64 GetBufferCount() {
		return m_count;
	}


	// must prepare before calling?
	Int64 ReadFromBuffer(Buf8 *buf, Int64 len) {
		CHECK_ARG_BUFFER(buf, len);
		CHECK_STATUS(m_count >= len);
		Mem::Copy(buf, p_buffer, len);
		Remove(len);
		return len;
	}

	Int64 Read(Buf8 *buf, Int64 len) {
		CHECK_ARG_BUFFER(buf, len);
		if(len <= m_count) {
			Mem::Copy(buf, p_buffer, len);
			Remove(len);
			return len;
		}
		else {
			Mem::Copy(buf, p_buffer, m_count);
			size_t nr;
			try {
				nr = p_stm->Read(buf + m_count, len - m_count);
			}
			catch(...) {
				if(m_count == 0)
					throw;
			}
			Int64 ret  =  m_count + nr;
			Remove(m_count);
			return ret;
		}
	}


	~BufferedStream() {
		delete[] p_buffer;
	}


};

class BinaryReader {
public:
	Stream*			p_stm;
	bool			m_own;
	BufferedStream	m_bufstm;

public:
	BinaryReader(Stream *pstm, bool own = false)
		: p_stm(pstm)
		, m_own(own)
		, m_bufstm(pstm)
	{
		// pstm not null && CanRead
	}

	virtual ~BinaryReader() {
		if(m_own)
			delete p_stm;
	}

	template<class T>
	void ReadIntegralOrFloat(T &out) {
		if(m_bufstm.PrepareBuffer(sizeof(T)) == BufferedStream::NotEnough) {
			throw IOException("End of file - not enough data from stream");
		}
		out = *(T*)m_bufstm.p_buffer;
		m_bufstm.Remove(sizeof(T));
	}

	void ReadString(String &out) {
		BufferedStream::ReturnType err;
		Int64 str_len = m_bufstm.PrepareBufferNewLine(err);
		if(err == BufferedStream::NotEnough)
			throw IOException("End of file");

		out = String(m_bufstm.GetBufferPtr(), str_len);

		switch (err)
		{
		case BufferedStream::NewLineByEOF:
			m_bufstm.Remove(str_len);
			break;
		case BufferedStream::NewLineByCR:
		case BufferedStream::NewLineByLF:
			m_bufstm.Remove(str_len + 1);
			break;
		case BufferedStream::NewLineByCRLF:
			m_bufstm.Remove(str_len + 2);
			break;
		}
	}

	void Read(bool &out) { ReadIntegralOrFloat(out); }
	void Read(Char &out) { ReadIntegralOrFloat(out); }
	void Read(UChar &out) { ReadIntegralOrFloat(out); }
	void Read(Short &out) { ReadIntegralOrFloat(out); }
	void Read(UShort &out) { ReadIntegralOrFloat(out); }
	void Read(WChar &out) { ReadIntegralOrFloat(out); }
	void Read(UInt &out) { ReadIntegralOrFloat(out); }
	void Read(Long &out) { ReadIntegralOrFloat(out); }
	void Read(ULong &out) { ReadIntegralOrFloat(out); }
	void Read(Int64 &out) { ReadIntegralOrFloat(out); }
	void Read(UInt64 &out) { ReadIntegralOrFloat(out); }
	void Read(float &out) { ReadIntegralOrFloat(out); }
	void Read(double &out) { ReadIntegralOrFloat(out); }
	void Read(long double &out) { ReadIntegralOrFloat(out); }
	void Read(String &out) { ReadString(out); }

	//template<>

	// ReadTo()
	// IgnoreTo()
	// SeekTo
	// SeekOver

	void SeekOver(const Buf8 *buf, Int64 len) {
		CHECK_ARG_BUFFER(buf, len);
		Int64 cut;
		if(m_bufstm.EnsureFound(cut, buf, len) != BufferedStream::Success)
			throw IOException("Buffer cannot be found or end of file");

		m_bufstm.Remove(cut);
	}

	template<class T>
	T Read() {
		T tmp;
		if(m_bufstm.PrepareBuffer(sizeof(T)) == BufferedStream::NotEnough) {
			throw IOException("Stream has not enough data");
		}
		m_bufstm.ReadFromBuffer((Buf8 *)&tmp, sizeof(T));
		return tmp;
	}

	StmLen Read(Buf8 *buf, StmLen len) {
		return m_bufstm.Read(buf, len);
	}
};

IO_END
